package au.com.finalconcept.roo.addon.behaviours;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import org.apache.commons.lang3.builder.ToStringBuilder;
import org.springframework.roo.classpath.PhysicalTypeIdentifierNamingUtils;
import org.springframework.roo.classpath.PhysicalTypeMetadata;
import org.springframework.roo.classpath.details.FieldMetadata;
import org.springframework.roo.classpath.details.FieldMetadataBuilder;
import org.springframework.roo.classpath.details.ItdTypeDetailsBuilder;
import org.springframework.roo.classpath.details.MethodMetadata;
import org.springframework.roo.classpath.details.MethodMetadataBuilder;
import org.springframework.roo.classpath.details.annotations.AnnotatedJavaType;
import org.springframework.roo.classpath.details.annotations.AnnotationMetadata;
import org.springframework.roo.classpath.details.annotations.AnnotationMetadataBuilder;
import org.springframework.roo.classpath.itd.AbstractItdTypeDetailsProvidingMetadataItem;
import org.springframework.roo.classpath.itd.InvocableMemberBodyBuilder;
import org.springframework.roo.metadata.MetadataIdentificationUtils;
import org.springframework.roo.model.DataType;
import org.springframework.roo.model.JavaSymbolName;
import org.springframework.roo.model.JavaType;
import org.springframework.roo.project.LogicalPath;
import org.springframework.uaa.client.util.Assert;

/**
 * This type produces metadata for a new ITD. It uses an
 * {@link ItdTypeDetailsBuilder} provided by
 * {@link AbstractItdTypeDetailsProvidingMetadataItem} to register a field in
 * the ITD and a new method.
 * 
 * @since 1.1.0
 */
public class RooSoftDeleteMetadata extends AbstractItdTypeDetailsProvidingMetadataItem {

    private static final String PROVIDES_TYPE_STRING = RooSoftDeleteMetadata.class.getName();
    private static final String PROVIDES_TYPE = MetadataIdentificationUtils.create(PROVIDES_TYPE_STRING);

    protected RooSoftDeleteMetadata(String identifier, JavaType aspectName, PhysicalTypeMetadata governorPhysicalTypeMetadata) {
        super(identifier, aspectName, governorPhysicalTypeMetadata);
        Assert.isTrue(isValid(identifier), "Metadata identification string '" + identifier + "' does not appear to be a valid");

        // Adding a new sample field definition
        builder.addField(getDeletedAtField());

        // Adding a new sample method definition
        builder.addMethod(getGetterMethod());
        builder.addMethod(getSetterMethod());
        builder.addMethod(getActiveDataMethod());
        builder.addMethod(getDeletedDataMethod());
        builder.addMethod(getRemoveMethod());

        // Create a representation of the desired output ITD
        itdTypeDetails = builder.build();
    }

    /**
     * Creates a method that returns all "active" (non deleted) items The method
     * sig is public static List<UserObject> UserObject.findActive(int
     * firstResult, int maxResults)
     * 
     * @return
     */
    private MethodMetadata getActiveDataMethod() {
        return this.getDataAccessMethod("findActive", "/findActive-template.txt");
    }

    /**
     * Creates a method that returns all "active" (non deleted) items The method
     * sig is public static List<UserObject> UserObject.findActive(int
     * firstResult, int maxResults)
     * 
     * @return
     */
    private MethodMetadata getDeletedDataMethod() {
        return this.getDataAccessMethod("findDeleted", "/findDeleted-template.txt");
    }

    private MethodMetadata getDataAccessMethod(String methodNameString, String templatePath) {
        // Specify the desired method name
        JavaSymbolName methodName = new JavaSymbolName(methodNameString);

        // Check if a method with the same signature already exists in the
        // target type
        MethodMetadata method = Util.methodExists(this.governorTypeDetails, methodName, new ArrayList<AnnotatedJavaType>());
        if (method != null) {
            // If it already exists, just return the method and omit its
            // generation via the ITD
            return method;
        }

        // Define method annotations (none in this case)
        ArrayList<AnnotationMetadataBuilder> annotations = new ArrayList<AnnotationMetadataBuilder>();
        AnnotationMetadataBuilder annotationBuilder;
        annotationBuilder = new AnnotationMetadataBuilder("org.springframework.transaction.annotation.Transactional");
        annotations.add(annotationBuilder);

        // Define method throws types (none in this case)
        List<JavaType> throwsTypes = new ArrayList<JavaType>();

        // Define method parameter types
        List<AnnotatedJavaType> parameterTypes = new ArrayList<AnnotatedJavaType>();
        List<AnnotationMetadata> annotationMetadataList;
        AnnotatedJavaType type;
        annotationMetadataList = new ArrayList<AnnotationMetadata>();
        type = new AnnotatedJavaType(JavaType.INT_PRIMITIVE, annotationMetadataList);
        parameterTypes.add(type);
        type = new AnnotatedJavaType(JavaType.INT_PRIMITIVE, annotationMetadataList);
        parameterTypes.add(type);

        // Define method parameter names (none in this case)
        List<JavaSymbolName> parameterNames = new ArrayList<JavaSymbolName>();
        parameterNames.add(new JavaSymbolName("firstResult"));
        parameterNames.add(new JavaSymbolName("maxResults"));

        // Create the method body
        InputStream stream = null;
        String line;
        String destObjecName;
        InvocableMemberBodyBuilder bodyBuilder = new InvocableMemberBodyBuilder();

        // Read from a template
        try {
            stream = this.getClass().getResourceAsStream(templatePath);
            BufferedReader reader;
            reader = new BufferedReader(new InputStreamReader(stream));

            destObjecName = this.destination.getSimpleTypeName();
            for (; (line = reader.readLine()) != null;) {
                // Replace the OBJECT_NAME markers
                line = line.replaceAll("\\$\\{OBJECT_NAME\\}", destObjecName);
                bodyBuilder.appendFormalLine(line);
            }
        } catch (Exception e) {
            e.printStackTrace();
            if (stream != null) {
                try {
                    stream.close();
                } catch (Exception e1) {
                    e1.printStackTrace();
                }
            }
        }
        // Use the MethodMetadataBuilder for easy creation of MethodMetadata
        JavaType returnType;
        List<JavaType> typeParams;
        typeParams = new ArrayList<JavaType>();
        typeParams.add(new JavaType(this.destination.getFullyQualifiedTypeName()));
        returnType = new JavaType("java.util.List", 0, DataType.TYPE, null, typeParams);
        MethodMetadataBuilder methodBuilder = new MethodMetadataBuilder(
                getId(),
                    Modifier.PUBLIC | Modifier.STATIC,
                    methodName,
                    returnType,
                    parameterTypes,
                    parameterNames,
                    bodyBuilder);
        methodBuilder.setAnnotations(annotations);
        methodBuilder.setThrowsTypes(throwsTypes);

        return methodBuilder.build(); // Build and return a MethodMetadata
                                      // instance
    }

    private MethodMetadata getRemoveMethod() {
        // Specify the desired method name
        JavaSymbolName methodName = new JavaSymbolName("remove");

        // Check if a method with the same signature already exists in the
        // target type
        MethodMetadata method = Util.methodExists(
                this.governorTypeDetails,
                    methodName,
                    new ArrayList<AnnotatedJavaType>());
        if (method != null) {
            // If it already exists, just return the method and omit its
            // generation via the ITD
            return method;
        }

        // Define method annotations (none in this case)
        ArrayList<AnnotationMetadataBuilder> annotations = new ArrayList<AnnotationMetadataBuilder>();
        AnnotationMetadataBuilder annotationBuilder;
        annotationBuilder = new AnnotationMetadataBuilder("org.springframework.transaction.annotation.Transactional");
        annotations.add(annotationBuilder);

        // Define method throws types (none in this case)
        List<JavaType> throwsTypes = new ArrayList<JavaType>();

        // Define method parameter types
        List<AnnotatedJavaType> parameterTypes = new ArrayList<AnnotatedJavaType>();
        List<AnnotationMetadata> annotationMetadataList;
        AnnotatedJavaType type;
        annotationMetadataList = new ArrayList<AnnotationMetadata>();
        type = new AnnotatedJavaType(JavaType.BOOLEAN_PRIMITIVE, annotationMetadataList);
        //parameterTypes.add(type);

        // Define method parameter names (none in this case)
        List<JavaSymbolName> parameterNames = new ArrayList<JavaSymbolName>();
        //parameterNames.add(new JavaSymbolName("soft"));

        // Create the method body
        InputStream stream = null;
        String line;
        String destObjecName;
        InvocableMemberBodyBuilder bodyBuilder = new InvocableMemberBodyBuilder();

        // Read from a template
        try {
            stream = this.getClass().getResourceAsStream("/remove-template.txt");
            BufferedReader reader;
            reader = new BufferedReader(new InputStreamReader(stream));

            destObjecName = this.destination.getSimpleTypeName();
            for (; (line = reader.readLine()) != null;) {
                // Replace the OBJECT_NAME markers
                line = line.replaceAll("\\$\\{OBJECT_NAME\\}", destObjecName);
                bodyBuilder.appendFormalLine(line);
            }
        } catch (Exception e) {
            e.printStackTrace();
            if (stream != null) {
                try {
                    stream.close();
                } catch (Exception e1) {
                    e1.printStackTrace();
                }
            }
        }
        // Use the MethodMetadataBuilder for easy creation of MethodMetadata
        MethodMetadataBuilder methodBuilder = new MethodMetadataBuilder(
                getId(),
                    Modifier.PUBLIC,
                    methodName,
                    JavaType.VOID_PRIMITIVE,
                    parameterTypes,
                    parameterNames,
                    bodyBuilder);
        methodBuilder.setAnnotations(annotations);
        methodBuilder.setThrowsTypes(throwsTypes);

        return methodBuilder.build(); // Build and return a MethodMetadata
                                      // instance
    }

    private FieldMetadata getDeletedAtField() {
        return Util.getDateField(this.getId(), "deletedAt", "deleted_at");
    }

    /**
     * Creates the getter for the deletedAt field
     * 
     * @return
     */
    private MethodMetadata getGetterMethod() {
        return Util.getGetterMethod(this.governorTypeDetails, Date.class, this.getId(), "getDeletedAt", "deletedAt");
    }

    /**
     * Creates the getter for the deletedAt field
     * 
     * @return
     */
    private MethodMetadata getSetterMethod() {
        return Util.getSetterMethod(this.governorTypeDetails, Date.class, this.getId(), "setDeletedAt", "deletedAt");
    }

    public static boolean isValid(String metadataIdentificationString) {
        return PhysicalTypeIdentifierNamingUtils.isValid(PROVIDES_TYPE_STRING, metadataIdentificationString);
    }

    public static final String getMetadataIdentiferType() {
        return PROVIDES_TYPE;
    }

    public static final String createIdentifier(JavaType javaType, LogicalPath path) {
        return PhysicalTypeIdentifierNamingUtils.createIdentifier(PROVIDES_TYPE_STRING, javaType, path);
    }

    public static final JavaType getJavaType(String metadataIdentificationString) {
        return PhysicalTypeIdentifierNamingUtils.getJavaType(PROVIDES_TYPE_STRING, metadataIdentificationString);
    }

    public static final LogicalPath getPath(String metadataIdentificationString) {
        return PhysicalTypeIdentifierNamingUtils.getPath(PROVIDES_TYPE_STRING, metadataIdentificationString);
    }

    // Typically, no changes are required beyond this point

    public String toString() {
        final ToStringBuilder builder = new ToStringBuilder(this);
        builder.append("identifier", getId());
        builder.append("valid", valid);
        builder.append("aspectName", aspectName);
        builder.append("destinationType", destination);
        builder.append("governor", governorPhysicalTypeMetadata.getId());
        builder.append("itdTypeDetails", itdTypeDetails);
        return builder.toString();
    }
}
